Spec-as-Source: The Level Nobody Reaches for Integration Birgitta Böckeler's October 2025 analysis of spec-driven development tools Kiro, spec-kit and Tessl sorted approaches into three levels — spec-first, spec-anchored and spec-as-source — and found that all SDD approaches she located are spec-first, with only Tessl explicitly targeting the third level, then in private beta. Böckeler reported generating code twice from the same Tessl spec and getting different results, and warned that spec-as-source could inherit "the downsides of both MDD and LLMs: Inflexibility and non-determinism." The article argues integration logic avoids those generator costs because it is mostly declarative rather than irreducible application logic. In October 2025, Birgitta Böckeler tried to pin down what “spec-driven development” actually means https://martinfowler.com/articles/exploring-gen-ai/sdd-3-tools.html . She looked at Kiro, spec-kit and Tessl and sorted them into three levels: 1. Spec-first — a spec is written first and used for the task at hand. 2. Spec-anchored — the spec survives the task and is maintained through evolution. 3. Spec-as-source — “the spec is the main source file over time, and only the spec is edited by the human, the human never touches the code.” Her verdict: “All SDD approaches and definitions I’ve found are spec-first, but not all strive to be spec-anchored or spec-as-source.” Only Tessl was explicitly reaching for the third level, and it was in private beta. That is a fair description of the ceiling — for application code . Integration is a different problem, and the level that is hard to reach there is close to free here. Why the third level is so hard for application code In Böckeler’s framing, spec-as-source has a specific shape: the human edits the spec, an LLM generates the code, and the generated files carry a header like // GENERATED FROM SPEC - DO NOT EDIT . She is openly sceptical, and points at model-driven development — the UML and DSL-to-code generators of the 2000s — for the parallel: “I wonder if spec-as-source, and even spec-anchoring, might end up with the downsides of both MDD and LLMs: Inflexibility and non-determinism.” She is right, and the mechanism is worth naming precisely. The difficulty is not the spec. It is the generator. Put one between the spec and the running system and you inherit three problems: non-determinism she generated code twice from the same Tessl spec and got different results , a second artifact to govern generated code still compiles, still has a CI pipeline, and can still be edited by someone who did not read the header , and drift two artifacts edited independently diverge; DO NOT EDIT is a social control, not a technical one . Every one of those costs is paid by the generator. None is paid by the spec. Where integration logic actually lives today Most integration is not on that ladder at all. There are two dominant habits, and both lose the artifact. The first is burying it in application code. The integration is not a thing anyone designed; it accreted. A retry here, a field rename there, an auth header copied from the service next door — spread across controllers and helpers until the only accurate description of what the system integrates with is the codebase itself. Nobody wrote a spec because nobody ever decided to build an integration. It arrived as boilerplate and set as spaghetti. The second is the visual builder — low-code and iPaaS canvases that pull the logic into a proprietary tool. That is a genuine improvement on option one: the flow becomes a thing, with a picture. But the artifact you get back is a canvas, not a contract. What it can reach is hard to enumerate without clicking through it, it diffs badly or not at all in review, and it runs only inside the vendor that drew it. The logic left the codebase and became a different kind of opaque /blog/beyond-ipaas-why-capabilities-are-the-right-unit/ . The shared failure is not verbosity or vendor lock-in. In both cases there is no artifact you can read to know what the integration does. That is what makes drift arguments feel abstract to most teams: you cannot drift from a spec you never had. Integration has no source files to generate Here is the asymmetry that makes a third option possible. An application has irreducible logic — business rules, algorithms, state machines — that must end up as executable instructions somewhere. If a human does not write them, a generator must. An integration mostly does not. Strip one down and what is left is a set of declarations: which upstream systems may be reached and under which credentials, which calls compose into one unit of work, which fields survive, and what surface is exposed to whom over which protocol. None of that is an algorithm — it is configuration of a runtime that already exists. When the spec contains no algorithms, there is nothing for a generator to generate , and the problems Böckeler identified disappear with it. So the third level is reachable by a different route: not spec → generator → code → runtime , but spec → runtime . Spec-Driven Integration https://shipyard.naftiko.io/ikanos/latest/concepts/spec-driven-integration/ states the step plainly — “Execute — run the engine against the specification; no code generation or compilation required.” That is the actual innovation — not that the file is YAML, but that the integration becomes a thing you can read . Buried code has no artifact; a canvas has one you cannot review or run elsewhere. A declarative capability is a text file: diffable in a pull request, greppable, lintable, versioned next to the services it touches, and executable as-is. Böckeler’s spec-as-source says the human never edits the code ; here there is no code to edit — one artifact, so no synchronisation problem and no drift, as a property rather than a discipline. We have made this argument before from the drift side /blog/specs-age-like-bananas-spec-driven-integration-as-the-cure/ . What the spec has to carry The catch is that “no generator” only works if the declarative file is genuinely complete. A spec that describes 80% of an integration and leaves the rest to a hand-written adapter is spec-anchored with extra steps. An Ikanos capability /glossary/capability/ is one YAML file with four top-level sections, and the test is whether they cover the whole path from upstream system to agent: ikanos: 1.0.0-beta6 consumes: which systems may be reached - import: crm from: ./shared/crm.yml - import: billing from: ./shared/billing.yml aggregates: how calls compose into a unit of work - namespace: support flows: get-customer-context: semantics: safe: true idempotent: true inputParameters: customer-id: type: string required: true steps: get-customer: type: call call: crm.get-customer with: { id: "{{customer-id}}" } list-invoices: type: call call: billing.list-invoices with: { customer: "{{customer-id}}" } mappings: - target: plan value: "$.get-customer.subscription.plan code" - target: unpaid-invoices value: "$.list-invoices.data ? @.status=='open' " exposes: the surfaces, from one definition - type: mcp namespace: support-copilot tools: get-customer-context: ref: support.get-customer-context - type: rest port: 8080 binds: credentials, separated from logic crm-token: env: CRM TOKEN Note how thin the declarative layer is. Steps run in order: call invokes an upstream operation, lookup joins against an earlier result in memory. References are Mustache templates for inputs and JSONPath for a previous step’s output — not a general expression language. That thinness is what makes the file reviewable: a reader can enumerate every upstream operation the capability can reach before anything runs — the thing neither buried code nor a canvas will tell you. Two qualifications keep that honest. Richer orchestration — conditional steps, for-each, parallel-join — is on the Ikanos roadmap, because real flows do branch. And a third step type already ships: script , running sandboxed JavaScript, Python or Groovy on GraalVM. Neither undermines the argument, because neither is a generator . A script step is read and executed at runtime like every other step — nothing is emitted, compiled or synchronised, so the drift mechanism never starts. The cost is real but different: a script lives in its own file, so that one step gives up the single-artifact property. The engine treats it accordingly — script execution can be disabled outright, and is bounded by a timeout and a statement limit. An escape hatch with a governance surface, not the substrate. The exposes section is where the absence of a generator pays a second dividend. Four exposer types ship — rest , mcp , skill and control — and they read the same aggregates block, so REST and MCP are two projections of one definition rather than two outputs that drift apart. This is the BFF pattern Sam Newman named in 2015 https://samnewman.io/patterns/architectural/bff/ , except declaring the surface means a new consumer is a new section, not a new service /blog/applied-capabilities-bff-for-context-engineering/ . The semantics block matters too. Declaring safe: true is not documentation — the engine derives the MCP tool hints from it, so safe becomes readOnlyHint and idempotent becomes idempotentHint on the tool the agent sees. A governance statement in the spec becomes a behavioural guarantee in the protocol. Where the alternatives actually sit Placed on Böckeler’s scale, the tools built for agentic integration cluster in the same two bands — for good reasons rather than bad ones. The distinction worth drawing is about what each treats as the source of truth , not which is better. | | Where the truth lives | Böckeler level | What it costs | |---|---|---|---| | Ikanos | One declarative capability file | Spec-as-source | Nothing to generate; the spec must be complete | | reShapr https://reshapr.io/docs/overview/how-it-works/ | Config plan over an existing service | Spec-anchored | The upstream API must already exist and still needs owning | | Jentic https://github.com/jentic/arazzo-engine | OpenAPI + Arazzo, brokered at call time | Spec-anchored | Discovery is dynamic; the surface is known only at runtime | | Apache Camel https://camel.apache.org/ | Java or YAML DSL routes | Spec-first / code | Routing configuration, not a tool definition; MCP needs a bridge | | FastMCP https://gofastmcp.com/ | Python or TypeScript handlers | Code-first | Logic is in the handler; REST means a second application | | iPaaS / low-code | A canvas in the vendor’s tool | Off the ladder | No reviewable artifact; runs only where it was drawn | reShapr and Jentic are not competitors in the way that table implies — they are downstream by design. reShapr describes itself accurately as “a zero-code AI translation layer” over existing APIs; Jentic brokers tools to agents just in time. Both answer “I have a running API and need agents to use it well.” Neither answers “what should the capability be in the first place?” FastMCP is genuinely the best developer experience for writing an MCP server. The cost shows up later: a decorated Python function is an MCP tool and only an MCP tool. When the same capability is needed over REST, the handler does not project — you write a second application, or try to lift the logic out and discover it was never separable. Apache Camel remains excellent at Enterprise Integration Patterns, and nothing here replaces it. Its YAML DSL declares routes — but it describes how messages move , not what a tool is and whether it is safe to call twice . The honest limits Two of Böckeler’s concerns survive even once the generator is gone. The spec review experience still has to be good. Her complaint about spec-kit — “I’d rather review code than all these markdown files” — applies to any approach that moves the reviewable artifact. A declarative file beats both a canvas and a folder of generated markdown, but “better” is not “solved”. That is why Polychro https://shipyard.naftiko.io/docs/ exists as a linter rather than an afterthought: if the spec is the only artifact, it needs the tooling code has had for twenty years. Completeness is a real constraint, not a marketing asterisk. The declarative layer has to carry the integration, and wherever it cannot, something else must — the exact pressure that makes declarative systems grow a scripting language and drift back toward code-first. Ikanos ships script for that pressure, deliberately fenced. The judgement is what belongs inside the fence: a field transformation, fine; a negotiation protocol or a stateful saga with compensation logic is an application, and belongs in a service the capability consumes . The honest formulation is not “no code anywhere”, it is code stays a bounded, declared, switch-off-able step rather than the thing the integration is made of . The SDI write-up treats that failure as diagnostic: “When a specification cannot be executed as-is, the gap is a signal of incompleteness — not an invitation for interpretation.” Allow interpretation — by a codegen step, a hand-written adapter, or an LLM at runtime — and you are back at spec-anchored, paying the costs Böckeler catalogued. The rule Spec-as-source is hard to reach for application code because a generator sits between the spec and the running system, importing non-determinism and a second artifact along with it. Integration is the domain where you can delete the generator — not because the spec is smarter, but because there was never anything to generate. An integration is declarations about systems, credentials, composition and surfaces, and a runtime can read those directly. That is the part worth taking away. Most integration today is either buried in application code or drawn on someone else’s canvas, and neither leaves you an artifact you can read. The top level of the maturity model turns out to be the easy one here — it just required noticing that the thing everyone was generating did not need to exist. Further reading - 🪜 Understanding Spec-Driven Development: Kiro, spec-kit, and Tessl https://martinfowler.com/articles/exploring-gen-ai/sdd-3-tools.html — Birgitta Böckeler’s three levels, and the MDD warning - 📐 Spec-Driven Integration https://shipyard.naftiko.io/ikanos/latest/concepts/spec-driven-integration/ — the methodology in full - 🧠 Structured-Prompt-Driven Development https://martinfowler.com/articles/structured-prompt-driven/ — Wei Zhang and Jessie Xia on governing AI-assisted change - 🧷 Backends For Frontends https://samnewman.io/patterns/architectural/bff/ — Sam Newman, 2015 - 🏗️ Kaspar von Grünberg https://platformengineering.org/authors/kaspar-von-gruenberg · Luca Galante https://platformengineering.org/authors/luca-galante — platform units as self-service assets - 🍌 Specs age like bananas /blog/specs-age-like-bananas-spec-driven-integration-as-the-cure/ — the drift argument in full - 🧮 Beyond iPaaS: why capabilities are the right unit /blog/beyond-ipaas-why-capabilities-are-the-right-unit/ - 🧱 Applied capabilities: the BFF pattern for context engineering /blog/applied-capabilities-bff-for-context-engineering/ - 📥 Importing your API into MCP is usually the wrong move /blog/importing-your-api-into-mcp-is-usually-the-wrong-move/ - ⚙️ Ikanos https://ikanos.io/ · 📖 Documentation https://shipyard.naftiko.io/docs/ · 🛝 Playground https://shipyard.naftiko.io/playground/