Tools
Tools: Filling the Unit System: IntegrationUnit, AdapterUnit, and the Full Boot Sequence
2026-02-25
0 views
admin
IntegrationUnit ## Two Phases, Two Contexts ## defineIntegration ## Init Order ## AdapterUnit — The Middle Layer ## The Full Boot Sequence (In Theory) ## A Note on AI and How I Work ## What Is Next Last post, the Unit System had its first real types — RuntimeUnit, PackageManager, and defineRuntime(). The runtime layer was done. But a runtime by itself does nothing. It knows how to run things, but nothing is asking it to run anything yet. The system needed the other two unit kinds — integrations and adapters — before any of it would actually connect. IntegrationUnit is the interface for framework-specific plugins — React, NestJS, Angular, Docker as an orchestrator, and anything else that wires into the Velnora lifecycle. If runtimes are the foundation and adapters are the build tools, integrations are the things that actually give a workspace its identity. The key design decision here was making all lifecycle hooks optional. An integration only implements what it needs. If it just needs to expose an API during init, it implements configure(). If it also needs to do something at build time, it adds build(). If it needs neither, it can still exist as a unit that just declares capabilities for others to depend on. This is Interface Segregation in practice. The Kernel duck-types before calling — 'configure' in unit — so there is no dead code, no empty method stubs, no forced implementations. Every unit kind now gets phased contexts. The configure() hook receives an IntegrationConfigureContext — full access to ctx.expose() and ctx.query(). The build() hook receives an IntegrationBuildContext — query-only, because by the time you are building, registration is closed. No new APIs can be exposed during a production build. Right now all three unit kinds share the same idea, but the context types are not fully separated yet. Runtimes, adapters, and integrations each have different phase-specific needs, and the concrete context interfaces still need to be split out per kind. That is a task for the next round. This separation is not just for safety. It makes the intent clear at the type level. If you are inside build(), TypeScript will not even let you call expose(). You know exactly what phase you are in by looking at the context type. Just like defineRuntime(), the factory is thin. You pass your integration definition, it injects kind: 'integration' and returns a VelnoraUnit. Same pattern, same type inference, same generics capturing literal strings from requires and capabilities. All three define helpers — defineRuntime(), defineAdapter(), and defineIntegration() — support a second calling convention: a factory function. Instead of passing a plain object, you can pass a function that receives ConfigEnv (command, mode, etc.) and returns the config. This is useful when you need to change behavior based on whether you are running dev or build. The factory wraps your function and injects kind at call-time. Both conventions produce the same thing. Static for simple cases, factory for environment-aware ones. The pattern is identical across all three unit kinds. In theory, integrations should initialize last — after runtimes, after adapters. The idea is that by the time an integration runs its configure() hook, every runtime is booted and every adapter is ready, so the integration can safely query anything it depends on. Within the integration tier, a topological sort by requires and optionalRequires would determine exact ordering. So if your integration depends on 'docker', the Docker integration would configure first. This is the design — the actual resolver is not wired up yet. One open question: what happens when A requires B and B requires A? A circular dependency. The honest answer is I have not decided yet. The resolver could reject it at startup, or it could try to break the cycle with a warning. For now the design assumes no cycles — if you create one, you get what you deserve. This will need a real answer before the resolver ships. With integrations done, the adapter was next. AdapterUnit represents build-tool orchestrators — Vite, Webpack, Turbopack, esbuild, and similar. Unlike integrations, adapters have required hooks: dev(project, ctx) and build(project, ctx). Both take a Project — the specific project directory the adapter is operating on — plus a typed context. An adapter must handle both modes. There is no optional here — if you are an adapter, you know how to start a dev server and you know how to produce a production build. And dev() returns a DevServerResult — connection info that the Host uses to wire up the dev server. But the biggest design shift from earlier iterations of the spec was removing the mode field. I originally had mode: 'thread' | 'process' on the adapter itself. That was wrong. Whether something runs in-thread or as a child process is not the adapter's decision — it is the runtime's decision. A Vite adapter should not care if it runs inside Node's process or gets spawned separately. It just says what to run. The runtime says how. Velnora executes the plan. So the adapter queries the runtime via ctx.query(), calls something like execute(), and the runtime returns a declarative ExecutionPlan — a discriminated union. In thread mode, it carries a run function that Velnora calls in-process. In process mode, it carries the binary, args, and cwd for Velnora to spawn as a child process. Either way, the adapter does not care which one comes back. Same adapter code works with Node, Bun, or any other runtime. The adapter never changes. Like integrations, adapters get phased contexts — AdapterDevContext for dev() and AdapterBuildContext for build(). And defineAdapter() follows the same pattern: static object or factory function, kind injected automatically, full type inference on dependencies. The init order is not implemented yet, but in theory this is how it should work: Within each tier, topological sort by requires and optionalRequires would determine exact ordering. You drop units into one array and the Kernel figures out the rest — that is the design. What actually happens in practice is a different story, but this is the contract the system is built around. All three unit kinds are now implemented. The runtime layer has RuntimeUnit, PackageManager, Toolchain, and defineRuntime(). The integration layer has IntegrationUnit, phased contexts, and defineIntegration(). The adapter layer has AdapterUnit, the execution model split, and defineAdapter(). The Unit System is complete — every unit kind has its interface, its factory, and its place in the boot sequence. I use AI for generating tests and JSDoc. In rare cases, code. And even when it generates code, I review it multiple times before applying it to the project and committing. The code architecture is mine. But the way it gets seen — which packages should exist, how the spec should read, how the design should be documented — that is where I use Notion AI. Each time I work on a feature, it takes almost a couple of hours: understand the design, consult with AI, analyze it again, and then document it properly. The thinking is mine. The documentation process is a collaboration. But right now I hit the limits. Almost every code assistant out there has usage caps — except Notion AI — and I have burned through all of them. That means tests and JSDoc generation are temporarily gone until I get a local assistant running. I have been setting one up, but it is not fully ready yet — right now I am working on creating a connection between my PC and my Mac so I can run and use it remotely, distance independent. The Unit System is complete — every unit kind has its interface, its factory, and its place in the boot sequence. The next step is wiring them into the Kernel's actual resolver and making the first real unit packages. That means real defineRuntime() calls for Node and Bun, a real defineAdapter() for Vite, and a real defineIntegration() for React. Interfaces are done. Now it is time to fill them. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse - Runtimes — language runtimes boot first
- Adapters — build tools next
- Integrations — frameworks last
how-totutorialguidedev.toaikernelserverdockernode