{"slug": "spec-as-source-the-level-nobody-reaches-for-integration", "title": "Spec-as-Source: The Level Nobody Reaches for Integration", "summary": "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.", "body_md": "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:\n\n1. **Spec-first** — a spec is written first and used for the task at hand.\n2. **Spec-anchored** — the spec survives the task and is maintained through evolution.\n3. **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.”*\n\nHer 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.\n\nThat 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.\n\n## Why the third level is so hard for application code\n\nIn 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:\n\n*“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.”*\n\nShe 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).\n\nEvery one of those costs is paid by the generator. None is paid by the spec.\n\n## Where integration logic actually lives today\n\nMost integration is not on that ladder at all. There are two dominant habits, and both lose the artifact.\n\n**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.\n\n**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/).\n\nThe 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.\n\n## Integration has no source files to generate\n\nHere 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.\n\nAn 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.\n\nSo 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.”*\n\nThat 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/).\n\n## What the spec has to carry\n\nThe 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.\n\nAn [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:\n\n```\nikanos: 1.0.0-beta6\n\nconsumes:            # which systems may be reached\n  - import: crm\n    from: ./shared/crm.yml\n  - import: billing\n    from: ./shared/billing.yml\n\naggregates:          # how calls compose into a unit of work\n  - namespace: support\n    flows:\n      get-customer-context:\n        semantics:\n          safe: true\n          idempotent: true\n        inputParameters:\n          customer-id:\n            type: string\n            required: true\n        steps:\n          get-customer:\n            type: call\n            call: crm.get-customer\n            with: { id: \"{{customer-id}}\" }\n          list-invoices:\n            type: call\n            call: billing.list-invoices\n            with: { customer: \"{{customer-id}}\" }\n        mappings:\n        - target: plan\n          value: \"$.get-customer.subscription.plan_code\"\n        - target: unpaid-invoices\n          value: \"$.list-invoices.data[?(@.status=='open')]\"\n\nexposes:             # the surfaces, from one definition\n  - type: mcp\n    namespace: support-copilot\n    tools:\n      get-customer-context:\n        ref: support.get-customer-context\n  - type: rest\n    port: 8080\n\nbinds:               # credentials, separated from logic\n  crm-token:\n    env: CRM_TOKEN\n```\n\nNote 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.\n\nTwo 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.\n\nNeither 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.\n\nThe `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/).\n\nThe `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.**\n\n## Where the alternatives actually sit\n\nPlaced 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.\n\n|  | Where the truth lives | Böckeler level | What it costs | \n|---|---|---|---|\n| **Ikanos** | One declarative capability file | Spec-as-source | Nothing to generate; the spec must be complete | \n| **[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 | \n| **[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 | \n| **[Apache Camel](https://camel.apache.org/)** | Java or YAML DSL routes | Spec-first / code | Routing configuration, not a tool definition; MCP needs a bridge | \n| **[FastMCP](https://gofastmcp.com/)** | Python or TypeScript handlers | Code-first | Logic is in the handler; REST means a second application | \n| **iPaaS / low-code** | A canvas in the vendor’s tool | Off the ladder | No reviewable artifact; runs only where it was drawn | \n\n**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?”*\n\n**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.\n\n**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*.\n\n## The honest limits\n\nTwo of Böckeler’s concerns survive even once the generator is gone.\n\n**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.\n\n**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**.\n\nThe 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.\n\n## The rule\n\nSpec-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.\n\nIntegration 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.\n\nThat 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.\n\n## Further reading\n\n- 🪜 [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\n- 📐 [Spec-Driven Integration](https://shipyard.naftiko.io/ikanos/latest/concepts/spec-driven-integration/) — the methodology in full\n- 🧠 [Structured-Prompt-Driven Development](https://martinfowler.com/articles/structured-prompt-driven/) — Wei Zhang and Jessie Xia on governing AI-assisted change\n- 🧷 [Backends For Frontends](https://samnewman.io/patterns/architectural/bff/) — Sam Newman, 2015\n- 🏗️ [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\n- 🍌 [Specs age like bananas](/blog/specs-age-like-bananas-spec-driven-integration-as-the-cure/) — the drift argument in full\n- 🧮 [Beyond iPaaS: why capabilities are the right unit](/blog/beyond-ipaas-why-capabilities-are-the-right-unit/)\n- 🧱 [Applied capabilities: the BFF pattern for context engineering](/blog/applied-capabilities-bff-for-context-engineering/)\n- 📥 [Importing your API into MCP is usually the wrong move](/blog/importing-your-api-into-mcp-is-usually-the-wrong-move/)\n- ⚙️ [Ikanos](https://ikanos.io/) · 📖[Documentation](https://shipyard.naftiko.io/docs/) · 🛝[Playground](https://shipyard.naftiko.io/playground/)", "url": "https://wpnews.pro/news/spec-as-source-the-level-nobody-reaches-for-integration", "canonical_source": "https://naftiko.io/blog/spec-as-source-the-level-nobody-reaches-for-integration/", "published_at": "2026-09-12 00:00:00+00:00", "updated_at": "2026-09-12 23:25:12.502839+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools", "ai-products", "large-language-models"], "entities": ["Birgitta Böckeler", "Kiro", "spec-kit", "Tessl", "Martin Fowler"], "alternates": {"html": "https://wpnews.pro/news/spec-as-source-the-level-nobody-reaches-for-integration", "markdown": "https://wpnews.pro/news/spec-as-source-the-level-nobody-reaches-for-integration.md", "text": "https://wpnews.pro/news/spec-as-source-the-level-nobody-reaches-for-integration.txt", "jsonld": "https://wpnews.pro/news/spec-as-source-the-level-nobody-reaches-for-integration.jsonld"}}