Case Study
TYLS
Fiscal platform for French furnished-rental landlords, built from the ground up and taken to launch in three months. I designed the architecture and the engineering system around it — then audited every change merged during a three-week absence and used the findings to harden that system.
Role
Tech Lead / Product Engineer
Timeline
3 months (ongoing)
Year
2026
Category
FinTech

Context & Constraints
TYLS is a tax-filing application for French non-professional furnished-rental landlords (LMNP) who declare under the actual-expenses regime (régime réel). It guides users through preparing their declaration, while an internal back-office supports the accounting workflow and produces the final tax return package (liasse fiscale).
The domain made correctness more important than raw delivery speed: sensitive financial and identity data, fiscal calculations, GDPR and European data-sovereignty requirements, payment and invoicing flows, and a seasonal filing cycle.
The delivery environment added its own constraints: a small engineering team with limited dedicated capacity, reduced project-management availability, and an aggressive schedule. The estimate also assumed substantial reuse of an existing proof of concept. After reviewing it, I rejected that assumption — it demonstrated feasibility, but its structure was not a viable production foundation. The scope and deadline did not change.
The Core Problem
The challenge was not only designing the application, but establishing a system in which several developers could work quickly without progressively weakening its architecture.
AI raised the stakes. It had dramatically increased implementation throughput, which made weak conventions more dangerous: a developer could now produce a large amount of locally plausible code before an architectural mistake was noticed. The development process had to be designed as deliberately as the application itself.
My Role & Ownership
I led the technical direction of the project from its production foundation onward.
My responsibilities included:
- System architecture and backend, frontend and data boundaries
- Security and data-isolation strategy
- Engineering conventions, ADRs and AI-assisted development workflows
- CI/CD, repository guardrails, and code and architectural review
- Delivery workflows and design-system foundations
- Hands-on implementation where the project needed reference patterns or extra capacity
I also stepped into gaps outside the nominal Tech Lead role when they blocked delivery. With limited project-management capacity, I introduced structured Jira templates and worked with QA to make ownership and verification states explicit. With no designer at the start, I established the initial token and component system until a designer took over the client-facing experience. I also built the first major section of the client declaration flow myself, to set concrete patterns and reusable components for the rest of the team.
The goal was to turn recurring decisions and predictable failure modes into explicit systems, so the team didn't have to solve the same problems from first principles — or route every decision through me. Process reduces coordination cost, but it can't add engineering capacity: the project stayed constrained by its original staffing and estimation assumptions, and the initial release moved beyond its original target.
Key Engineering Decisions
The application is a TypeScript monorepo: two Vue applications, separate Fastify BFFs for the client and back-office surfaces, a NestJS backend, shared contracts and UI packages, and PostgreSQL. The decisions below follow one principle: assume mistakes will happen, and constrain their blast radius.
1. Fail-closed data isolation
Access isolation is enforced in PostgreSQL with Row-Level Security rather than relying on every application query to remember the right tenant or user constraint. Access context is transaction-scoped, and missing context exposes no protected rows instead of accidentally broadening access.
That moves a high-consequence security invariant below the application layer.
2. Domain ownership over shared database convenience
Backend modules own their persistence. Cross-domain dependencies go through explicit services rather than arbitrary cross-module table access, and hard foreign keys stay primarily inside module boundaries.
The cost is additional ceremony. The benefit is knowing where each business invariant belongs, and preventing convenient joins from gradually turning the backend into a distributed monolith inside one process.
3. Fiscal logic as a pure boundary
Fiscal calculations live in fiscal-core, a pure package with no database or infrastructure dependencies. The same logic powers client-side previews while the backend remains authoritative, so monetary representation, rounding and fiscal rules have one explicit home.
Testing effort follows risk rather than uniform coverage: thousands of automated tests, including real PostgreSQL integration tests for isolation and concurrency behavior.
4. Explicit contracts at system boundaries
Runtime boundaries are validated with Zod rather than trusting TypeScript types that disappear at runtime. AI integrations follow the same rule: model output is treated as untrusted external input, validated against contracts, and backed by a manual fallback before it can become business state.
AI is part of the implementation, not a privileged source of truth.
5. Conventions encoded as layered controls
The development workflow separates reasoning from execution: the implementation agent inspects the relevant code paths before writing code, and isolated review precedes human judgment. Jira tickets carry product intent, constraints and acceptance criteria; implementation planning happens where the code is. That split came from experience — early tickets carried technical specifications written without full repository context, and they occasionally contradicted the actual architecture.
Rules are encoded across layers: AI context and path-specific rules, Claude Code hooks and review skills, ESLint dependency boundaries, generators, git hooks, tests, GitLab CI, project-specific AI review, and human architectural review. No single layer is expected to prove correctness. For example, accepted ADRs are historical records, not mutable documentation; when repeated disagreement showed that rule was ambiguous, I added a guard preventing AI tooling from silently rewriting them. A new decision supersedes the old one instead of erasing the history behind it.
The broader operating model is covered in AI as an Engineering Discipline.
6. Deliberately deferred controls
A production foundation doesn't justify every possible quality mechanism. Storybook was deferred: there was no authoritative visual design to validate stories against until a designer joined mid-project, and adding it then would have competed with delivery. Browser E2E coverage was deferred as a schedule lever, leaving real residual risk around cookies, CSRF and route integration despite extensive unit, integration and database-level testing. Mutation testing on the fiscal engine didn't yet justify its computational and schedule cost.
Each was a decision about where the next unit of engineering effort reduced the most risk — with the residual risk named rather than ignored.
The Stress Test I Didn't Plan
The most useful test of this system happened while I was away for three weeks.
During that period, the project moved to a new repository on the client's GitLab. Critical repository settings were not carried over: mandatory approval was lost, authors could merge their own changes, and the merge strategy I had established was no longer enforced. Feature development continued; review discipline did not.
When I returned, the repository contained patterns the previous workflow should have intercepted: missing MR descriptions, inconsistent process metadata, repeated corrective changes, and implementation issues that should never have reached develop. Rather than sample a few changes, I audited all 37 merge requests created since the migration — automated governance checks plus AI-assisted code review, with every AI finding independently challenged before being retained.
The results:
- None of the 37 met the intended review standard
- 19 contained a blocker or substantive code issue
- No MR had received a single review comment
- 28 of the 35 merged MRs had been merged by their own author
- 7 needed a follow-up MR to correct something
The defect count mattered less than the distribution. Rules backed by enforcement were generally respected. Rules that depended on convention or an optional review step degraded quickly.
Concluding that developers had stopped following the process would have been the shallow reading. The useful question was why the process could disappear without the system objecting. Static analysis, architecture rules, tests and committed tooling travel with the repository. Repository settings lived in GitLab configuration, and review depended on people choosing to do it. The migration crossed those boundaries without anyone verifying that the new repository still met the project's governance requirements.
Elsewhere in TYLS, missing security context fails closed. Repository governance did the opposite: missing configuration silently granted more freedom. That was the design flaw — and it was mine.
The audit also confirmed a boundary automation can't cross. A system can constrain execution; it cannot manufacture judgment. It can reject an invalid dependency or an unsafe operation, but it can't make an engineer ask why an invariant exists before removing it. The point of enforcement is to stop spending human judgment on what machines can check reliably.
Outcomes & Impact
TYLS goes into its first filing season with:
- A production architecture built from the ground up in three months, in place of the original proof of concept
- User data isolation enforced in PostgreSQL and designed to fail closed
- Fiscal rules isolated in a pure, heavily tested package that the backend treats as authoritative
- Engineering rules layered from local hooks and AI tooling through CI to human review
- A complete audit of every change merged after the repository migration
Fixing the 37 MRs repairs the current repository. Fixing the conditions that let them through changes the system. Findings are being remediated by severity, and I'm hardening the shared controls around the failure classes the audit exposed: restoring repository governance, moving recurring process requirements into automated verification, and extending AI-assisted checks where repeated findings can be detected mechanically.
The lesson I'm keeping: fail closed applies to engineering workflows too. Losing a critical control should stop or restrict delivery, not silently make the process more permissive.
Tech Stack
- Frontend: Vue 3 (client app + back-office)
- BFF: Fastify (separate client and back-office BFFs)
- Backend: NestJS, PostgreSQL (Row-Level Security)
- Shared: TypeScript monorepo, Zod contracts,
fiscal-core, shared UI packages - CI/CD & Infrastructure: GitLab CI, Kubernetes
- AI: Mistral AI (product integrations), Claude Code (development workflow)