Factory apps validation — Synthesis & Decision
Recommendation: Option C — Two-Lane Thin Factory. Build 4 things (work ledger, triage layer, fleet monitor, dual-run harness) ON Zaruba’s substrate under a CODEOWNERS-partitioned lane; scope the knowledge graph down to an audit-evidence extractor; drop the rewrite engine, scaffolder, standalone decommission app, and MBUS bridge. Confidence: high on the portfolio shape and the drops; medium on placement (inside monorepo-development’s internal plane vs Robert-owned repo) — that hinges on assumption A1 (Tomas accepts the partitioned lane), with A-lite (own repo, API-only integration) as the named fallback.
Context
SSH access to groupon2 landed 2026-07-18 and changed the ground truth: Zaruba’s greenfield is a rebuild-from-audit program (legacy = telemetry evidence, never ported; dual-run/shadow to parity; kill gates), not the mass-porting program the original batch-1/2 research assumed. He has already built the two-plane Encore architecture, a tasks human-in-the-loop service, a full Temporal workflow-management platform, and an MCP/docs skeleton [01-deep-dive [E1]–[E3]]. Infra is 0% provisioned behind a human-only billing gate [E5]. The legacy sample (1.05M active backend LOC) shows MBUS load-bearing, EOL runtimes, and two half-finished stranglers rotting in code [E6]. Tomas is decision-bound; review capacity is the system’s scaling constraint; Robert’s committed lane is execution tooling + fleet monitor [E8].
Options considered
| Option | One-liner | Weighted score |
|---|---|---|
| A — Full Factory | All 9 apps standalone in a Robert-owned plane | 2.40 |
| B — Zaruba-Native Extension | No factory; thin extensions inside Zaruba’s repos only | 3.35 |
| C — Two-Lane Thin Factory | 4 builds on Zaruba’s substrate, partitioned lane, rest scoped/dropped | 4.45 |
| D — Defer-Most | Monitor + assessment now, everything else after M4 | 3.00 |
Decision matrix
| Criterion | Wt | A | B | C | D |
|---|---|---|---|---|---|
| Simplicity & operability | 20% | 2 — two Temporal platforms, 9 apps to run (04-A-pros#1,#5) | 4 — fewest parts, but Robert’s fleet ops degraded by gated timelines (04-B-pros#3) | 4 — one substrate, 3 thin services + 1 real build (03-C) | 3 — simple, but fleet runs unmanaged/manual (04-D-adv concession) |
| Agentic-development fit | 20% | 3 — full control but two convention islands to teach agents (04-A-adv concession 2) | 3 — inherits strong conventions; agent-scale features blocked on human-gated paths (04-B-pros#2) | 5 — one convention set, typed Encore+Drizzle+PG, structural partition [E2] | 2 — fleet without ledger/triage is illegible to operate (03-D) |
| Domain fit (DDD) | 15% | 2 — duplicate task/workflow bounded contexts (04-A-pros#1,#3) | 3 — Robert owns no context; semantic squatting in Tomas’s aggregates (04-B-cross) | 5 — work-unit lifecycle is a real, Robert-owned context; human-task + workflow invariants stay in Zaruba’s aggregates (03-C) | 2 — no new contexts at all |
| Evolution & headroom | 15% | 3 — can grow, but shadow-platform dead-end risk (04-A-pros#8) | 2 — hard ceiling: agent-scale features must argue into core-plane services (03-B) | 4 — per-app droppable; lanes extend to old-estate 1→N split later (03-C) | 3 — max optionality, idle capability |
| Testability (TDD) | 10% | 4 — full isolation, but harness only contract-testable across repos | 4 — inherits vitest discipline | 5 — house vitest+Docker-PG pattern + harness contract tests vs legacy lab (03-C) | 3 — monitor layer only |
| Delivery speed | 10% | 1 — months to first value (03-A) | 4 — days, but gated on billing/reviews (04-B-pros#3) | 4 — weeks (03-C) | 5 — immediate, tiny scope |
| Cost | 5% | 1 — 9 apps + dup ops (03-A) | 5 — near-zero marginal (04-B-adv#7) | 4 — 3 thin + 1 real build (03-C) | 5 — minimal |
| Risk & reversibility | 5% | 2 — 9-app sunk-cost surface (04-A-adv#5 cuts both ways) | 3 — reversible commits, but capability gaps accrue (04-B concessions) | 4 — each app independently droppable; A-lite fallback (04-C-cross) | 4 — nothing to back out, but drift risk unbounded (04-D-pros#8) |
| Weighted total | 2.40 | 3.35 | 4.45 | 3.00 |
Sensitivity: C leads B by 1.10 — no ±5pp swap of adjacent weights flips the winner (largest movement ~0.15). Decision is robust; no prose tiebreaker needed.
Per-app verdicts (the actual question)
| # | App | Verdict | Shape |
|---|---|---|---|
| 1 | Migration ledger / orchestrator | BUILD THIN | One Encore service, internal plane, Postgres SSOT (unit × state × attempts × cost × blocking-question × disposition). One-way projection from workflow-management/Temporal events — Temporal stays the only execution truth. Absorbs the claim-before-work queue (research/12) and the decommission ledger as row types. |
| 2 | Question-triage | BUILD-ON-ZARUBA | Robert-owned dedup/rate-limit/knowledge-write-back service in the internal plane; Tomas’s tasks service gets ONE negotiated PR (agent-question TaskType). The portfolio’s R&D bet — unsolved in public literature [E7]. Ship naive exact-match dedup first, embeddings second. |
| 3 | Fleet monitor | ADOPT + thin layer (confirmed, unchanged from research/12) | OTel→Prometheus→Grafana, fork claude-code-otel, dashboard 25255, local-first (docker-compose) until the billing gate clears; thin layer = rework rate, cost/merged-unit, queue depth, stall detection. Robert’s committed deliverable — first to ship. |
| 4 | Verification / dual-run harness | BUILD — flagship (promoted again) | Staged scope: Orders-domain adapter generalizing Zaruba’s 3-oracle reconciliation first; generalize only per flagship gate. Includes the legacy-runtime lab (pinned EOL images) and MBUS shadow-taps where a domain’s parity requires event comparison. |
| 5 | Codebase knowledge graph MCP | SCOPE DOWN | Legacy-audit evidence extractor (tree-sitter over legacy sample, feeds HOT/COLD audits). Productize to MCP only if audit demand recurs at flagship 2. Extraction quality gets verified sampling — it feeds irreversible scope decisions (04-C-pros#6). |
| 6 | Declarative rewrite engine | DROP | Doctrine ports no legacy code [E1][E4]. Re-decision trigger: old-estate 1→N Encore split enters Robert’s lane. |
| 7 | Service scaffolder | DROP | Zaruba’s skills cover it. Re-decision trigger: factory needs a 5th service. |
| 8 | Decommission ledger | MERGE into #1 | Disposition row type per legacy component (MIGRATED/DROPPED/DEFERRED/DEAD + owner + date). Directly targets the in-house failure mode: two rotting half-stranglers [E6]. |
| 9 | MBUS bridge | DROP as app | Harness-internal shadow-taps only. A standalone bridge reverses Zaruba’s explicit rejection [E4] — fastest route to collision (04-A-pros#2). |
Recommended architecture
flowchart LR
subgraph Z[monorepo-development — system of record]
subgraph CP[core plane — Tomas DRI]
ZT[tasks svc]; ZW[workflow-management]; ZO[reconciliation oracles]
end
subgraph IP[internal plane — factory lane, Robert DRI via CODEOWNERS]
WL[work-ledger svc<br/>PG SSOT + claim queue + dispositions]
QT[triage svc<br/>dedup / rate-limit / write-back]
VH[dual-run harness<br/>shadow diff + parity kernel]
end
end
subgraph OBS[observability — local-first, cloud when billing clears]
G[(Grafana + thin fleet layer)]
end
QT -- one negotiated TaskType PR --> ZT
ZW -- events, one-way projection --> WL
VH -- generalizes per flagship --> ZO
VH -- shadow taps --> LAB[legacy runtime lab<br/>pinned EOL images]
KG[audit evidence extractor] --> WL
Fleet[Robert's agent fleet] -- claims units --> WL
Fleet -. OTel .-> G
Critical flow — dual-run parity (highest-risk flow)
sequenceDiagram
participant L as work-ledger
participant F as fleet agent
participant H as harness
participant LG as legacy lab
participant N as new service (greenfield)
L->>F: claim unit (atomic)
F->>H: request characterization
H->>LG: replay recorded traffic (+ MBUS shadow-tap if needed)
H->>N: same traffic
H->>H: diff responses + events (3-oracle pattern)
alt parity < threshold
H->>L: unit blocked + evidence
L->>QT: mint agent-question task
else parity ≥ threshold
H->>L: unit verified → PR lane
end
Evolution path
flowchart LR V1[v1: monitor local + ledger + naive-dedup triage + Orders harness adapter] V2[v2: cloud observability + embedding dedup + Deal-Factory harness adapter] V3[v3: KG productized to MCP + old-estate 1toN lanes in ledger] V1 -- billing gate clears AND M4 gate passes --> V2 V2 -- audit demand recurs at flagship 2 AND ledger >500 units --> V3
Risk register (from the winner’s prosecution, 04)
| Risk | Likelihood | Detection signal | Mitigation / accepted |
|---|---|---|---|
| Harness doesn’t generalize past Orders (04-C-pros#1) | Medium | 2nd domain adapter costs >2× the first | Staged scope per flagship gate; shared kernel minimal; accept per-domain adapters as the product |
| Ledger drifts from Temporal execution state (04-C-pros#2) | Low–Medium | Weekly reconciliation audit mismatches | One-way projection by construction; Temporal sole execution truth; ledger owns only business fields |
| Triage graft resented / TaskType PR stalls (04-C-pros#3,#4) | Medium | PR unmerged >2 weeks | Layer lives in Robert’s service; fallback: standalone TaskType-free inbox, integrate later |
| A1 fails — no partitioned lane granted | Medium | No OWNERSHIP.md agreement within 2 weeks of the Tomas conversation | Named fallback: A-lite (Robert repo, API-only) — same portfolio, different placement |
| Audit-extractor errors propagate into signed scope (04-C-pros#6) | Medium | Spot-check disagreement rate >5% | Verified sampling on every extraction batch; confidence tags per fact (Zaruba’s own MEASURED/MODELED convention) |
| Fleet sized beyond evidence (64-concurrency ceiling) [E9] | High if ignored | Rework rate rises with concurrency | Ramp 10→50→64 gated on ledger metrics; 1000 = queued units, never concurrent editors |
Pre-mortem (12 months later, it failed because…)
- The lane never existed. Tomas didn’t want factory services in his repo; negotiation dragged; Robert built nothing for a quarter. Detection: A1 unresolved 2 weeks after the answer draft (research/14) is sent. Response: trigger A-lite immediately — the portfolio is placement-agnostic by design.
- Harness became the schedule. Every domain needed bespoke oracles; parity infrastructure consumed the year; fleet throughput never mattered. Detection: adapter-cost curve rising at domain 2. Response: cap harness investment per flagship; fall back to impact-selected tests (~70% cut [E9]) where full parity is uneconomic.
- Two sources of truth. Ledger and Temporal disagreed during an Orders incident; trust in both collapsed. Detection: first reconciliation mismatch. Response: projection-only invariant enforced by code review rule + weekly audit query.
- Triage shipped clever, not useful. Embedding dedup was tuned for weeks while stakeholders drowned; questions/unit never fell. Detection: questions/unit flat after 4 weeks. Response: naive-first ordering is in v1 by decision, not preference.
- Blind cloud scale-up. Billing cleared, fleet scaled to hundreds concurrent, rework exploded — the 64 ceiling was real. Detection: rework rate vs concurrency curve. Response: concurrency gated on ledger metrics per ramp plan; hard cap until data says otherwise.
Assumptions
| # | Assumption | Fallback |
|---|---|---|
| A1 | Tomas accepts a CODEOWNERS-partitioned factory lane in the internal plane | A-lite: Robert-owned repo, API-only integration (portfolio unchanged) |
| A2 | No generic dual-run machinery exists yet beyond merchant-accounting oracles | Harness scope shrinks to adapters — still Robert’s |
| A3 | Billing gate clears in weeks | Local-first observability is the product until then |
| A4 | Legacy telemetry access for shadow-recording obtainable | Static characterization + recorded-fixture replay until then |
| A5 | Sima’s groupon2 role unknown | Re-run collision protocol with 3 parties if active |
Program hardness — re-score
Easier than the batch-1/2 blueprint assumed, harder in different places. Gone: 65M-LOC codemod lane, mass porting, MBUS bridge, 1000-concurrent orchestration (replaced by 1000 queued units at ≤64 concurrent). Zaruba’s substrate removes ~2 apps of build. Remaining hardness concentrates in: (1) harness generalization across EOL estates — the hardest technical piece and now the flagship; (2) the two-lead integration — political/collaboration risk is the new top risk, mitigated structurally (CODEOWNERS lane, one-way projections, single negotiated PRs); (3) legacy-runtime resurrection for characterization; (4) triage dedup R&D. Robert’s factory build: weeks, not months (3 thin services + staged harness + dashboards). Fleet spend stays dominated by execution tokens, anchored by Bun ($165k/1M LOC) — the greenfield rebuild consumes far fewer agent-edit tokens than the old porting model implied.
Architect review addendum (grpn-eng:encore-architect, 2026-07-18, post-decision)
Portfolio shape confirmed; four corrections adopted:
- Projection source (HIGH): no core→internal event path exists (decision log #36: “no cross-plane signal bridge yet”). Work-ledger projects from the internal-plane workflow execution stream (workflow-proxy interceptor:
_ensureInProgress,_progressUpdate,workflowStatusUpdate…) — fleet unit lifecycles run as internal-plane workflows on Zaruba’s worker framework. Zero core-plane asks. - Ledger scope: claim/attempts/blocking-question reuse Temporal task queues + tasks service natively (
TaskWorkflowStamp); work-ledger keeps ONLY business fields (cost, disposition, evidence links) + external-claim endpoint for non-Temporal Claude agents. - Triage v1 = zero PRs: existing
INFORMATION_REQUEST+assigneeGroupSlug“agent-questions” queue. TaskType enum PR demoted to optional v2. Real negotiation = inbox pollution, solved by dedup/rate-limit + queue filter. - Harness shape: parity kernel + legacy-runtime lab = workspace package + CLI/CI runner outside Encore (Cloud Run can’t run pinned EOL images or tap MBUS); Encore surface = parity-result rows in work-ledger. Legacy creds stay in the lab, never in Encore secrets.
Also: name is always work-ledger (merchant-accounting owns “ledger”); lead the A1 ask with OWNERSHIP.md + directory convention, CODEOWNERS later on his startup-mode schedule; check old-estate _core_system/ai-agents overlap answer before ledger lands; recorded legacy traffic = PII/PCI → security-architect sign-off on fixture retention/masking; explicit columns not defaultTableColumns for append-only rows, keyset pagination, registerInternalService for each new service. Cross-plane clients stay lazy (app ids empty until billing/linking) — v1 runs on local encore run.
First implementation steps (TDD)
- Failing test first:
work-ledger— vitest integration test: claim-unit is atomic under 2 concurrent claimers (Docker PG, advisory lock); then the projection test: replayed workflow-management event stream produces exactly-once ledger transitions. - Walking skeleton: ledger service with 3 endpoints (claim / transition / block-with-question) + Grafana panel reading its queue-depth view — deployable local-first, no billing gate dependency.
- Monitor v1: docker-compose OTel+Prometheus+Grafana with forked claude-code-otel config;
OTEL_RESOURCE_ATTRIBUTESper-worktree tags; dashboard 25255 imported; first custom panel = cost/merged-unit fed by ledger. - Triage v1: contract test — same normalized question fingerprint twice → second auto-resolves from first’s answer (exact-match dedup); only then the TaskType PR to Tomas.
- Harness v1: characterization kernel test — record fixture from legacy lab (one EOL Rails endpoint in a pinned container), replay against a stub, diff = zero; then wire the Orders-domain adapter against Zaruba’s oracle interfaces.
- Send research/14 answer (Robert approves first) with the A1 ask embedded — the lane, OWNERSHIP.md, and the one TaskType PR as the only three asks.