Skip to main content
Welcome. This site supports keyboard navigation and screen readers. Press ? at any time for keyboard shortcuts. Press [ to focus the sidebar, ] to focus the content. High-contrast themes are available via the toolbar.
serard@dev00:~/cv

A Design-in-Public Stress-Test

Posture. This series is the thinking that decides whether packages/intentions and packages/extensions/* should ever exist. It is written before the implementation, in the register established by homelab-k8s and ide-dsl — explicit hypotheses, explicit counter-arguments, open questions left open, no false closure. If the diagnostic in Part 10 comes back null, the series ends and no packages are built.

The pressentiment

I have built @frenchexdev/requirements-* — a seventeen-package stack covering kernel decorators, AST scanners, mutation-scoped behavioral checks, pluggable workflow styles, traceability matrices, CLI wizards, and a strict-mode compliance gate. It works. It is dog-fooded by this site. It runs on every build via npx requirements compliance --strict.

And yet something nags.

The nag is this: every decorator in the kernel — @Verifies, @Satisfies, @Refines, @Expects — points outside itself. They are arrows. Arrows from a test method to an acceptance criterion. Arrows from a feature to a requirement. Arrows from a requirement to its parent. The kernel is a graph of references with no named source. The arrows all point somewhere, but the place they all come from has no name.

The hypothesis I want to stress-test is that this unnamed source is what I will call an intention, and that requirement itself is just one of its projections — one extension in a family. A type can be an extension of an intention. A class can be. A method, a function, a module, an application, an entire type-system can be. Requirement — the verifiable contract — is one kind among many, not the root.

If that is true, then @frenchexdev/requirements-* is mis-named. It should have been @frenchexdev/extensions-requirement from the start, sitting in a packages/extensions/ directory next to its peers. The shared kernel I would have written is not a requirements-shared-kernel but an extensions-shared-kernel — and upstream of that, a tiny intentions package that gives the arrows a place to start from.

If that is false, the whole exercise is etymological dress-up. "Intention" adds nothing that "purpose" or "goal" or "rationale" didn't already say in plain English, and the package proliferation is its own punishment.

This series is how I tell the difference.

Why now

The honest answer is that @frenchexdev/requirements-* shipped too fast. The seventeen packages came out of a four-month sprint of mostly-correct decisions, and the velocity itself is suspicious. Decisions made that fast are decisions that compressed a question instead of answering it. The thing I want to surface here is the question that got compressed.

The other honest answer is that I have been writing two other series in parallel — ts-source-generator on Roslyn-style codegen pipelines, and ide-dsl on the language-IR-as-contract pattern — and both of them have started using the word intention informally, in passing, as if everyone knew what it meant. They don't. I don't. That informal usage is exactly the sign that a concept is trying to surface and has not yet been named cleanly enough to be argued with.

The right time to write a design-in-public series is the moment when an informal word has accumulated enough load that it cannot continue to be informal without becoming dishonest.

What this series is not

This series is not a roadmap. There is no commitment that any package described in Part 9 will be implemented. The architecture sketched there exists to be argued with, not built from.

This series is not a sales pitch for a new abstraction. If the hypothesis fails the diagnostic in Part 10, the conclusion is "the abstraction is decorative, do not ship," and the series itself is the artifact that records why a tempting move was correctly refused.

This series is not a critique of the existing @frenchexdev/requirements-* packages. They work. They will keep working whether or not the intention/extension reframing turns out to be useful. The question is whether something additional would be unlocked, not whether what exists should be torn down.

This series is not finished thinking. Each article ends with at least one open question that the next article may or may not answer. Some questions are deliberately left dangling all the way to Part 10, and a few are left dangling past Part 10 because honest design-in-public sometimes means writing down the questions you could not answer in the time you gave yourself.

Map of the ten parts

  1. The Pressentiment — Why requirement is not the root. The arrows in the kernel point outward; where do they come from?
  2. In-tentio × Ex-tensio — Etymology is not decoration. The Latin pair, the categorical move, and an explicit defence of "generative metaphor" over "truth claim".
  3. The Extensions Already Here — An inventory of things we already build that are implicitly extensions of unnamed intentions: types, classes, methods, modules, package boundaries.
  4. Requirement, Repositioned — Re-reading @frenchexdev/requirements-shared-kernel under the hypothesis. The dissolution test: does this change anything we would write differently tomorrow?
  5. TypeExtension — When the type is the projection. A walkthrough of Intention<UserAuthentication>UserCredentials, AuthSession, AuthError. The "DDD value objects with decorator paint" objection.
  6. Class, Method, Function Extensions — The fine-grained projections. The cross-cutting case (one intention, many classes). The "you've reinvented AOP" objection.
  7. Module & Application Extensions — The coarse-grained projections. The "at this granularity, extension is just implementation" objection. The requirements-styles precedent for multiple competing projections of one kernel.
  8. Type-System Extension — When the kind of types matters. Effect types, capability types, branded types as projections. The "Haskell envy in TypeScript clothing" objection.
  9. Proposed Package Architecture — If this ships, what does the tree look like? intentions/, extensions/, the migration question for the existing requirements-* family. The "package proliferation for its own sake" objection.
  10. What to Ship First — How would we know we're wrong? One tiny diagnostic, one explicit exit clause. If the diagnostic is null, the series ends here and no packages are built.

The open questions the series promises to leave on the table

These are the questions I want to not resolve too early. If by Part 10 any of them have been resolved by accident, I have probably cheated.

  1. Is intention a noun (an object in the world) or a relation (a directionality of code towards an end)? The series flips on this more than once.
  2. Can two intentions share an extension? If yes, the model is a many-to-many graph, not a tree.
  3. Does an extension know the intention it projects from, or only the intention know its extensions? The direction of the pointer changes the API surface drastically.
  4. Is Requirement an extension kind (peer with Type, Class, etc.) or an extension instance (a single Requirement is one extension of one intention)? Both readings are coherent and they imply different package layouts.
  5. Does the kernel need a source generator (codegen-time), or is decorator metadata at runtime enough? Part 5 leans codegen, Part 6 leans runtime, and the disagreement is not accidental.
  6. Where does an intention come from? If from a stakeholder, the model rejoins requirements engineering. If from the developer's reading of the domain, the model rejoins DDD. If from neither, what?
  7. Can @frenchexdev/requirements-* keep its name and gain an extensions-requirement thin adapter, or must it be renamed? The cost of either choice is asymmetric and political.
  8. Is the right test of value "does it change code I would write tomorrow" (pragmatist) or "does it name something I could not name before" (linguistic)? The series oscillates and the oscillation is the point.

Falsification criteria

The hypothesis dies if any of the following holds, and I commit in advance to recognising the death:

  • The diagnostic in Part 10 shows that re-projecting an existing @Feature through an Intention produces a bit-identical artifact with no new information surfaced.
  • Every concrete @TypeExtension, @MethodExtension, @ModuleExtension sketched in Parts 5-7 can be rewritten as a plain decorator with no Intention<T> reference, and the rewriting loses nothing.
  • The "package proliferation" objection in Part 9 cannot be answered with a concrete payoff that the existing requirements-* stack does not already deliver.
  • The "generative metaphor" defence in Part 2 collapses into "we relabelled purpose as intention and nothing else changed."

If two or more of these fire, the series ends with "the hypothesis was a wrong intuition correctly investigated" — and that ending is also a successful design-in-public outcome. The shape of a discarded idea is itself a contribution to the shape of the next idea.

Let's start.

⬇ Download