manifest / design-doc · v0.1 · 2026
Docs as code DITA migration MVP
MANIFEST v0.1  ·  DITA to docs-as-code migration  ·  2026

MANIFEST: Migrate DITA to Markdown
without losing content

Disclaimer. This is a portfolio design document. Google has not engaged, sponsored or endorsed this work. The sample content is adapted from the Google Cloud TPU documentation, which Google publishes under the Creative Commons Attribution 4.0 licence5. The adapted version is modified from the original. Stakeholders are job roles, not real people. The two readers in §3 are fictional.

3
Markdown output formats
DITA-OT already supports
5
checks every topic
must pass before merge
0
unreported content losses
allowed per topic
Abstract

Google's data center products and services documentation is written in DITA XML. DITA lets writers reuse content. A writer writes a warning or a spec value once and references it from many topics. The team now wants to move to docs as code: Markdown files in Git, reviewed in pull requests and built by CI (continuous integration).

Converting DITA to Markdown is easy. DITA Open Toolkit (DITA-OT), the free publishing engine the team already uses, does it with one command1. The hard part is what the conversion removes without a warning. It copies shared content into every topic, writes key values as plain text and drops element IDs. No team can read thousands of converted pages by hand, so these losses reach readers.

MANIFEST is a migration agent. It converts one DITA map at a time and proves that the converted topics are complete. Gemini runs the migration and fixes problems. Fixed Python checks decide whether each topic passes. Topics that pass go into a pull request for a writer to approve. Topics that fail go to a writer with the reason.

DITADITA-OTdocs as codeMyST MarkdownSphinxcontent reuseGeminiAgent Development KitCloud Runhuman review

§1 Client Brief

Google publishes documentation for its data center products and services, including Cloud TPU (Tensor Processing Unit) accelerators. This MVP uses a small part of the Cloud TPU documentation as its sample: the pages for four TPU generations (v5e, v5p, v6e and TPU7x), rewritten as DITA topics.

These pages are a good test because they repeat a lot of content. Each generation has the same spec table with different values. The same values appear in tables and in text. Different readers need different parts of the page. In DITA, writers handle this with three features:

DITA featureWhat it doesExample in the TPU pages
Content reference (conref)Reuses an element, such as a note or a table row, from another topic.One note about slice shapes, used in every generation's topic.
KeyStores a value once in the map. Topics refer to the key, not the value.tpu7x-chips-per-pod = 9,2164
Conditional contentMarks content for an audience. A DITAVAL file includes or excludes it at build time.Capacity-planning notes shown only to operators.
Figure 1
System context: as-is state
DITA PUBLISHING DOCS AS CODE Technical writers XML editor DITA source maps · keys · conrefs DITA-OT HTML5 · PDF Reference docs site readers · support links Software engineers code editor Markdown in Git values typed by hand Developer docs site CI build manual copy, no check
The dashed red line is the gap. Content moves from DITA to Markdown by hand, and nothing records what changed.

§2 Problem Statement

DITA-OT can publish DITA as Markdown in three formats: standard Markdown, GitHub-Flavored Markdown and GitBook1. The current release is 4.42. The team does not need a new converter. It needs to know what the converter changed.

2.1 What conversion removes

Before DITA-OT writes any output, it processes the source. It replaces each key reference with its value, copies each conref target into place and removes content that the DITAVAL file excludes3. The output is correct for one audience, but the reuse is gone. Nothing in the Markdown shows which text was shared or which values came from keys.

The GitHub format loses more. DITA-OT turns definition lists into bulleted lists and leaves out attribute blocks that hold IDs and class names1.

Figure 2
What happens to each DITA feature in DITA-OT's Markdown output
DITA featureIn the Markdown outputEffect after migrationStatus
ConrefThe shared text is copied into each topic.One change becomes many edits.Lost
Key referenceThe value is written as plain text.Copies of a value drift apart.Lost
Conditional contentOnly one audience's version is kept.Other readers lose content.Lost
Element IDDropped in the GitHub format.Cross-references and support links break.Lost
Definition listBecomes a bulleted list in the GitHub format.The structure changes.Changed
Paragraphs, steps, code, simple tablesConverted as expected.None.Kept
Sources: DITA-OT Markdown output1 and pre-processing3 documentation. The conversion does not report any of these changes.
Example from a live page
The TPU7x page gives HBM capacity as 192 GiB in its comparison table and as 192 GB in the next section. It gives HBM bandwidth as 7,380 GBps in the table and as about 7.37 TB/s in the text4. When one value is typed in two places, the copies can drift apart. A key stores the value once.
2.2 Why the current options fail
OptionWhat it doesWhat it missesResult
Convert by handWriters copy each topic into Markdown.Slow. Reuse is lost. No record of changes.Too slow
DITA-OT Markdown outputConverts a whole map with one command.Loses reuse, IDs and conditions without a warning.Silent loss
DITA-OT and spot checksReviewers read a sample of converted pages.Pages outside the sample are never checked.Partial
MANIFESTConverts, restores reuse, checks every topic, then fixes or escalates.Cannot map some advanced DITA features. It sends these to a writer (§5.4).Checked
Problem statement
The Google data center documentation team can convert DITA to Markdown with tools it already has, but it cannot prove that the converted content is complete. The conversion removes reuse, element IDs and conditional content, and it does not report these losses. Checking every page by hand is too slow for thousands of topics. As a result, the team cannot migrate at scale with confidence.

§3 User Profiles

Five roles at Google run or depend on the migration. Two readers depend on the result.

Stakeholders
Docs Platform LeadSponsor
GoalMove the documentation to docs as code without lowering quality.
Measured byTopics migrated that pass all checks; time from code release to published docs.
TodayPilots one map by hand and reads a sample of pages.
ConcernApproves a migration she cannot fully review.
SuccessOne report per map that shows what passed, what failed and why.
Senior Technical WriterWriter
GoalKeep reuse: change a value once and see it change everywhere.
Measured byEdits per change; accuracy errors per release.
TodayMaintains conrefs and keys in the XML editor.
ConcernPast migrations turned one source into many copies.
SuccessReuse arrives in Markdown as reuse, not as copied text.
Information ArchitectWriter
GoalKeep the content model (topic types, maps, IDs) after the move.
Measured byStructural errors after migration.
TodayDocuments the content model in a wiki.
ConcernSpecialised elements become plain paragraphs without anyone noticing.
SuccessEvery element that has no Markdown equivalent is flagged for review.
Software EngineerContributor
GoalUpdate the docs in the same pull request as the code.
Measured byTime from merge to published docs.
TodayFiles a ticket for the docs team, or edits the Markdown site and lets it drift.
ConcernDoes not want to learn DITA to fix a typo.
SuccessPlain Markdown, a local preview and a check that names what broke.
Technical Support EngineerSupport
GoalLinks in past support cases and runbooks still open the right section.
Measured byBroken links reported; time to answer a case.
TodayKeeps a personal list of pages that moved.
ConcernA customer opens an old link and gets a 404 error.
SuccessEvery old link opens the right page or redirects to it.
Readers
Priya NatarajanML engineer, Google
TaskSizes a TPU7x slice for a training run. Reads the spec table, then the topology page.
ConcernIf two pages give different values, she trusts neither.
SuccessThe same value on every page, from one source.
Tomás ReyesSite reliability engineer, Google
TaskPlans network capacity. Bookmarks sections on the topology pages.
ConcernHis bookmarks break, or he sees content written for another audience.
SuccessHis bookmarks still work, and he sees only content for operators.

§4 User Stories

Each story comes from the Goal and Success fields in §3. The acceptance criteria are what the checks in §5 test.

US-01 · Docs Platform Lead
As the Docs Platform Lead, I want one report for each map that lists the topics that passed and failed and why, so that I can approve a migration without reading every page.
Accepted when: every map has a report · every failure links to the topic and the check that failed · no topic is skipped.
US-02 · Senior Technical Writer
As a senior technical writer, I want every conref and key to arrive as an include or a substitution, so that I can still change a value in one place.
Accepted when: includes in the output equal conrefs in the source · substitutions equal key references · any difference fails the topic.
US-03 · Information Architect
As an information architect, I want elements that have no Markdown equivalent sent to a writer, so that no structure is lost without a decision.
Accepted when: each unmapped element creates a review item · zero elements are downgraded without review.
US-04 · Software Engineer
As a software engineer, I want a check on my pull request that tells me if I broke a link, an include or a key, so that I can fix docs without a writer.
Accepted when: the check runs on every pull request · each error gives the file, the line and the fix.
US-05 · Technical Support Engineer
As a support engineer, I want every old URL and anchor to open the right page or redirect to it, so that links in past cases keep working.
Accepted when: each element ID is kept as an anchor · a redirect file is created for each map · links that cannot be resolved are listed by name.
US-06 · Priya (reader)
As an ML engineer, I want a spec value to be the same wherever it appears, so that I can size a slice from any page.
Accepted when: every keyed value comes from one substitution · the output has no typed copies of a keyed value.
US-07 · Tomás (reader)
As a site reliability engineer, I want to see only operator content and keep my bookmarks, so that the docs stay useful after the move.
Accepted when: the text for each audience matches DITA-OT's output for the same DITAVAL file · old anchors still resolve.
Figure 3
Where each story is tested
01 Inventory 02 Convert 03 Restore reuse 04 Check topic 05 Check links US-01 · US-03 no story US-02 · US-06 US-02 · US-03 · US-07 US-04 · US-05 US-01 is also met by the report the agent writes at the end of each map.
No story depends on the converter. DITA-OT already does that part well.

§5 Proposed Design

MANIFEST does one thing. It migrates a DITA map to MyST Markdown and proves that every topic is complete. It is an agent built with Agent Development Kit (ADK), Google's open-source agent framework. ADK is licensed under Apache 2.0 and its agents can run on Cloud Run14. Gemini 3.8 Flash (gemini-3.8-flash) runs the agent15 and calls its tools through Gemini function calling16.

The agent has five tools. Each tool is plain Python and gives the same result every time. The agent decides which tool to run next and how to fix a failed topic. The checks decide whether a topic passes. The agent cannot change a check result.

5.1 How it works
Figure 4
System context: MANIFEST added
Technical writer migrate tpu.ditamap DITA source Git · unchanged MANIFEST agent ADK · gemini-3.8-flash · Cloud Run inventory_map convert_map restore_reuse check_topic check_links fix (Gemini) tools: plain Python · fix: Gemini DITA-OT 4.4 markdown · html5 Migration report Cloud Storage · JSON + HTML Pull request passing topics · writer approves pass Review issue failing topics · with the reason fail after 2 fixes
Compare with Figure 1. The DITA source and DITA-OT stay as they are. The manual copy is replaced by the agent, and every topic now has a report.
5.2 Agent tools

1 · Count what is in the source inventory_map

Reads the map and its topics with Python and lxml. Counts elements, conrefs, key references, conditional attributes, element IDs and links in each topic. This count is the "before" picture that every check uses.

Output: inventory.json for each topic

2 · Convert with DITA-OT convert_map

Runs DITA-OT 4.4 with the standard markdown format, not the GitHub format, which drops more1. Before the run, the tool puts a placeholder in place of each conref, key reference and conditional block in a working copy. DITA-OT cannot resolve a placeholder, so the reuse survives the build. The DITA source is never changed.

Output: Markdown with placeholders · to test first in the MVP: placeholders pass through DITA-OT unchanged

3 · Put the reuse back restore_reuse

Replaces each placeholder with its MyST Markdown equivalent. Shared content becomes an include file. A key becomes a substitution. Conditional content becomes an only block. Each element ID becomes a target. §5.4 lists every mapping.

Output: MyST Markdown · _includes/ · substitutions file · build tags

4 · Check the topic check_topic

Runs three checks. Counts: counts the output again and compares it with the inventory. Text: builds the source with DITA-OT HTML5 and the output with Sphinx for the same DITAVAL file, then compares the text. IDs: confirms that every source ID is an anchor. Returns pass, or a list of differences.

Output: pass or fail, with the element and line for each difference

5 · Check the links check_links

Runs lychee, an open-source link checker, over the Markdown and the built HTML. It checks internal links offline and writes the result as JSON11. It also checks that every old URL has a redirect.

Output: broken links · redirect file for the map
5.3 Agent loop

The agent runs tools 1 to 5 for each topic. When check_topic reports a difference, Gemini reads the difference and the source, and proposes a fix. For example, it maps an element that has no rule yet. The agent applies the fix and runs the check again. After two failed fixes, it stops and opens a review issue for a writer. The issue contains the source, the output and the check result.

When a writer approves a fix, MANIFEST saves it as a new mapping rule. The next map uses the rule without calling Gemini.

Figure 5
What happens to one topic
Convert andrestore reuse Check topiccounts · text · IDs pass Add to pull request fail Gemini proposes fixattempt 1 of 2 check again 2 fails Writer reviewsapproved fix becomes a rule
Gemini is called only for topics that fail a check. Topics that pass on the first run never use the model.
5.4 Reuse mapping

MyST Markdown supports substitutions, set in the project configuration or in a page's front matter8, and an include directive18. Sphinx's only directive includes content based on tags set at build time9. Together they cover the three DITA reuse features.

DITAMyST MarkdownHow the check proves itMVP
ConrefInclude file in _includes/, written onceIncludes in the output equal conrefs in the sourceMapped
Key referenceSubstitution, for example {{ tpu7x_chips_per_pod }}Substitutions equal key references; no typed copiesMapped
Conditional content and DITAVALonly block with build tags; one build for each DITAVAL fileText matches DITA-OT output for the same DITAVAL fileMapped
Element IDTarget, for example (slice-shapes)=Every source ID is an anchorMapped
Specialised elements, conref push, key scopesNo ruleSent to a writer as a review issueWriter

One limit to know: Sphinx still processes content inside an only block that a build excludes, which can cause surprises when headings or links sit inside one10. The text check in tool 4 is there to catch this.

5.5 Why Sphinx and MyST
OptionReuse supportResult
Sphinx with MyST MarkdownIncludes, substitutions and build tags are built in. Python, like the rest of MANIFEST.Chosen
Material for MkDocsReaches end of life on 5 November 20266.Not chosen
DocusaurusReuse works by importing MDX files. The page's table of contents does not include headings from imported Markdown7.Not chosen
5.6 Where it runs
PartServiceNote
AgentCloud Run job, one map per runFree tier: 180,000 vCPU-seconds a month13
Pull request checkGitHub ActionsStandard runners are free for public repositories12
ReportsCloud Storage, US regionFree tier: 5 GB-months13
ModelGemini API, gemini-3.8-flashThe free tier is acceptable here because the sample content is public. Free tier content is used to improve Google products, so private content needs the paid tier17.

§6 Migration Simulator

The simulator runs three sample topics through MANIFEST. Each run shows the tools in order and the log that the agent writes. The runs are scripted to show the design. They do not call Gemini or DITA-OT.

manifest · migration simulator
idle
Sample topic
inventory_map
count the source
·
convert_map
DITA-OT · markdown
·
restore_reuse
placeholders → MyST
·
check_topic
counts · text · IDs
·
fix
Gemini · only on failure
·
check_links
lychee · redirects
·
result
pull request or review issue
·
// choose a sample topic and run the migration
Scripted demo. No live model or DITA-OT call. Counts are illustrative.

§7 Success Metrics

The MVP runs on the TPU sample map. It succeeds if the numbers below are met on that map. The first metric matters most: a migration that reports every loss is safe to review, even when it has failures.

MetricHow it is measuredMVP target
Unreported lossesLosses a writer finds after merge that the report did not list0
Topics checkedTopics with a report ÷ topics in the map100%
Reuse keptIncludes and substitutions in the output ÷ conrefs and key references in the source100% of mapped features
IDs keptSource element IDs that are anchors in the output100%
Broken linkslychee result after migration, including old URLs0
Topics passed without a writerTopics that pass on the first run or after a Gemini fixSet from the first run; tracked for each map
Fixes approvedGemini fixes that a writer approves ÷ fixes proposedTracked; each approved fix becomes a rule
Figure 6
Sample migration report for one map (illustrative values)
tpu-generations.ditamap · run 00071 topic needs review
24topics
21passed first run
2passed after fix
1sent to a writer
TopicCountsTextIDsLinksNote
create-tpu-vmpasspasspasspassNone
tpu7x-specspasspasspasspass3 includes, 9 substitutions restored
v6e-specsfixedpasspasspassDefinition list mapped by Gemini; approved
slice-planningfailpasspasspassSpecialised element has no rule; issue #41
The report is the evidence for US-01. A writer reviews one issue instead of 24 pages.

Sources

Checked on 19 October 2026.

  1. DITA Open Toolkit 4.3, "Generating Markdown output." dita-ot.org/4.3/topics/dita2markdown
  2. DITA Open Toolkit, "Migrating to release 4.4." dita-ot.org/4.4/topics/migrating-to-4.4
  3. DITA Open Toolkit, pre-processing modules: debug and filter, branch filtering, keyref, conref and profile steps. dita-ot.org/dev/reference/preprocess-debugfilter
  4. Google Cloud, "TPU7x (Ironwood)," last updated 13 October 2026. docs.cloud.google.com/tpu/docs/tpu7x
  5. Google Developers, "Site Policies." developers.google.com/site-policies
  6. mkdocs-material 9.7.7 release notes (17 July 2026), quoted in a dependency update. github.com/cms-cat/mkdocs-material/pull/229
  7. Docusaurus, "MDX and React: Importing Markdown." docusaurus.io/docs/markdown-features/react
  8. MyST-Parser, "Syntax extensions: Substitutions." myst-parser.readthedocs.io/en/stable/syntax/optional.html
  9. Sphinx only directive reference. devopstutodoc.readthedocs.io
  10. MongoDB Meta Documentation, "cond Directive." mongodb.com/docs/meta/reference/cond
  11. lychee link checker. snapcraft.io/lychee · github.com/lycheeverse/lychee-action
  12. GitHub Docs, "How use of GitHub Actions is measured." docs.github.com/en/billing/concepts/product-billing/github-actions
  13. Google Cloud, "Free Tier." cloud.google.com/free
  14. Google, Agent Development Kit documentation README. github.com/google/adk-docs
  15. Google AI for Developers, "Gemini models." ai.google.dev/gemini-api/docs/models
  16. Google AI for Developers, "Function calling with the Gemini API." ai.google.dev/gemini-api/docs/function-calling
  17. Google AI for Developers, "Gemini Developer API pricing." ai.google.dev/gemini-api/docs/pricing
  18. MyST-Parser changelog (include directive in MyST documents). github.com/executablebooks/MyST-Parser