INTERLOCK, release orchestration control plane

Role creator · Where: Metafora / CarrierDirect · Built: June–July 2026 Stack: .NET · C# · React + Vite + TypeScript · OpenAPI contract-first · Authentik OIDC · Bicep

Deploy is not release.

INTERLOCK assembles dependency-ordered release candidates from work that is provably ready, validates them on isolated ephemeral environments, promotes them to production deployed-but-dark at zero traffic, and exposes them to users only by deliberate, attributed feature-flag flips.

It uses a railroad metaphor end to end: tickets are cars, a release candidate is a consist, the ephemeral environment is a siding, production is the main line.


How it was built, the AI angle

This is the project I point at when someone asks what AI-assisted development actually changes.

  1. Design handoff, not a wireframe. The product started as six HTML/CSS/JS prototype screens produced in Claude Design. Those prototypes, not a PRD, were the source of product truth.
  2. Reverse-engineered into a spec. Using GitHub Spec Kit, the prototypes were turned into a real spec: 8 user stories, FR-001…FR-054, a research pass across 11 technology areas with explicit feasibility verdicts, a data model, a glossary, and a task list.
  3. Contract-first, then behavior. The OpenAPI document is the single source of truth for every data shape; .NET and TypeScript DTOs are generated from it. Shapes get frozen, the shell gets pressure-tested, then behavior iterates.
  4. The AI was made to argue with the design. The research pass found three things the prototypes promised that the platforms cannot actually deliver as drawn, and the ADRs adopt the corrected design instead of the prettier one:
    • Sidings are host/subdomain-isolated, not "header-routed", Azure Container Apps has no header matcher (ADR-0001).
    • "Contract fully implemented" is a two-tier check: static API-gateway parity caps out at AMBER; behavioral tests on the siding decide READY (ADR-0002).
    • Live-dark is owned by flags-off + expand/contract, not by the traffic weight (ADR-0003), and a human signature alone does not make a destructive migration safe.

Measured 123 commits across all branches in ~3 weeks, 105 of them (85%) co-authored by Claude. Highest AI-co-authorship share of any project here, and the one with the most non-negotiable correctness core.

What stayed human the readiness algebra. ReadinessEngine (six conditions, strict precedence, evaluates all dependencies), TopologicalOrder, VersionPolicy, and LifecycleStateMachine are hand-specified and covered by 34 domain tests. Every integration adapter sits behind a port and is cleanly stubbed. The line between "real + tested" and "typed seam" is documented, not fudged.


The screens

Every screenshot below is captured from the running stack by a Playwright spec and regenerated when the UI changes. If a screen changed and its screenshot didn't, the change isn't done, that rule is in the project constitution.

The Yard: what's ready, and why

The INTERLOCK Yard board: work grouped as READY TO BOARD, AMBER, and BLOCKED, each card showing its branch, PR number, and the evidence behind its verdict, above role cards for Conductor, Engineer, and Yard worker

All work on hand, grouped by readiness. Green boards freely. Amber means one condition is soft ("Jira: In Progress, QA has not signed off", "PR #209 open: awaiting review, 2 approvers required") and can be force-included only with a logged, attributed reason. Red means a hard condition fails ("ES-391 is amber, not ready", "Actions #1857: contract-diff test flaky, 2 of 5 runs red") and cannot board at all.

Each card traces its verdict back to an external source of truth: Jira, GitHub, CI, the API gateway, LaunchDarkly, and shows that evidence inline rather than asking you to trust a rollup. The Yard Crew strip along the top states who is allowed to do what: the Conductor decides whether an amber override goes through and signs it, the Engineer drives the consist through the dark deploy onto the main line, the Yard worker couples ready cars and unblocks the blocked ones. Roles come from IdP group membership, so the picture and the permissions can't drift apart.

The Consist Wizard, assemble a release candidate

The Consist Wizard, step one: pick work. Green cars board in one click; the dark CONSIST SO FAR sidebar shows the rc tag and the ordered train

Four steps: Pick work → Couplings → Tag → Confirm. Green cars board with one tap; dependencies auto-couple in topological order and the version auto-bumps (v0.42.0-rc.1 here). The dark CONSIST SO FAR sidebar shows the LOCO and the ordered train beneath it.

Every amber car opens an inline "Why force this amber car onto the consist?" box: minimum character count, logged with your identity, before FORCE ONTO CONSIST unlocks. Blocked cars have no such affordance at all. The guardrail isn't a policy someone might follow; it's the only path through the UI.

The Release cockpit, validate → promote → expose → file

The Release cockpit: a lifecycle stepper stopped at Promoted live-dark, three feature flags awaiting individual flips, and a live dispatch log streaming phase.changed, stage.pass, migration.held, migration.approved, and traffic.shifted events

Validate runs the checks on a siding. Promote deploys at weight 0 and holds at the migration gate for a named approver; once approved, traffic shifts to 100% with every flag still off: present, serving, observed by no user. Expose flips one flag at a time (that is the real release). File stamps the Jira fixVersion, appends the immutable ledger, and reopens the yard. Every transition publishes a dispatch event over SSE, so the run is observable as it happens: in this capture, migration.heldmigration.approved by Pat Promotertraffic.shifted to 100% (flags still off, live-dark). Three flags sit below the stepper, each with its own FLIP ON; the release happens one row at a time.

The amber DEV CONTROL PANEL at the bottom is the part I'd point at in an interview. It exists only in local/dev mode and lets you pick a scenario (happy-path, ci-flaky, migration-rejected, promotion-fails-rollback, promote-queued, needs-rebase, rolled-back-recover) or poke the mocked externals live: knock ES-527 CI red, LD control plane DOWN, reject next migration, main line BUSY (queue), trunk moved (needs rebase), and watch the system react in the dispatch log. Building the failure modes as first-class, drivable states is what let the unhappy paths get designed rather than discovered.

The unhappy paths are first-class

Rollback recovery: a guarded rollout regressed, the flag auto-turned off, and the cockpit offers re-expose or abandon
Rolled back: a guarded rollout regressed and the flag auto-turned off. The fix is forward-fix + flag-off: re-expose each flag to return to released, or abandon the consist back to the yard.
Promote queued: another consist holds the main line, so this promotion waits in FIFO order rather than racing
Promote queued: production is one main line, so promotions serialize. When another consist holds it, a promote queues (FIFO) instead of racing. A stale train must rebase before it can re-validate.

Why it matters

Most "release tooling" conflates deploying a binary with exposing a behavior, which is why rollbacks are scary and audits are archaeology. INTERLOCK separates the two, makes every override attributed, and treats queueing, rebasing, and rollback as normal states with a documented way back, not as incidents.

Provenance. Every screen above is a Playwright capture from the running containerized stack against the seeded reference backlog: no live customer tenant, no customer data. The product's own branding is what you see; the ES- prefix is the seed's synthetic ticket key.