Skip to content

Commit 53723cc

Browse files
dmealingclaude
andcommitted
docs(ADR-0027): polyglot docs composition — per-language apiSurfaces, model once
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 2a76f4f commit 53723cc

2 files changed

Lines changed: 81 additions & 0 deletions

File tree

spec/decisions/ADR-0025-unified-docs-door.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
**Status:** Accepted
44
**Date:** 2026-06-06
5+
**Extended by:** ADR-0027 (polyglot docs composition — the single api surface generalizes to a per-language `apiSurfaces[]` list)
56
**Extends:** ADR-0021 D1 (single docs door — now covers ALL docs, not just neutral)
67
**Revises:** ADR-0022 Part 3 (api-docs is the *api surface* of the docs door, not a
78
standalone `meta gen` generator)
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# ADR-0027: Polyglot docs composition — per-language api surfaces, model once
2+
3+
**Status:** Accepted
4+
**Date:** 2026-06-06
5+
**Extends:** ADR-0025 (unified docs door) — generalizes its single api surface to a
6+
per-language list so a polyglot solution gets one cross-linked doc tree.
7+
**Relates to:** ADR-0020 (Tier-1 per-port vs Tier-2 shared).
8+
9+
> This is **SP-1** of a two-part program. SP-2 adds the Java api surface emitting
10+
> into this contract (its own ADR/spec). This ADR records the composition contract;
11+
> it does not build any non-TS surface.
12+
13+
## Context
14+
15+
A polyglot solution (e.g. TypeScript + Java over the same meta models) needs ONE
16+
coherent doc tree: the **model docs once** (language-independent) + **one SDK/api
17+
reference per language** (idiomatic), all cross-linked. ADR-0025 unified the docs
18+
door but documented a single api surface. Generating the model docs once per
19+
language would duplicate language-neutral output (the Tier-2 trap); documenting
20+
only one language ignores the others.
21+
22+
## Decision
23+
24+
Documentation surfaces are declared as an explicit **per-language list** in the
25+
`docs:` config:
26+
27+
```ts
28+
docs: {
29+
outDir, layout, baseUrl,
30+
surfaces, // model/api on-off selector
31+
apiSurfaces: [ { lang, subDir, baseUrl? } ] // default [{ lang:"ts", subDir:"api" }]
32+
}
33+
```
34+
35+
- **Model once, api per language.** The model surface (Tier-2 shared engine) is
36+
generated once; each entry in `apiSurfaces` is a per-language api surface
37+
(Tier-1) under its own `subDir`. The model page links **every** declared surface
38+
(`**API reference:** [TypeScript](api/ts/Order.md) · [Java](api/java/Order.md)`).
39+
- **Explicit list, not auto/manifest.** This matches the proven pattern for
40+
one-model→many-language tooling — buf (`buf.gen.yaml` `plugins[]`, per-language
41+
`out:`) and Smithy (`smithy-build.json` projections/plugins) — and our own
42+
`generators[]`/`targets{}`. A multi-repo **manifest** (Backstage-catalog style)
43+
is a portal/federation layer ABOVE this; it is **deferred**. The internal
44+
resolver consumes a source-agnostic resolved surface list, so a manifest can feed
45+
the same contract later without reworking the generators.
46+
- **Cross-link base: relative or `baseUrl`.** `apiSurfaceHref` returns a relative
47+
path when the surface is in the same tree (one-tree topology) and an absolute
48+
`baseUrl/page` when the surface declares a `baseUrl` (federated / separate repo).
49+
Same code path yields both topologies.
50+
- **Per-port command, shared model engine** (inherited from ADR-0025). Each port's
51+
`docs` command emits the api surfaces whose `lang` it owns + links all declared;
52+
the model engine stays singular/shared (NOT reimplemented per language). For a
53+
polyglot TS+Java solution the model docs come from the TS toolchain once, so the
54+
deferred non-TS model-engine-reach decision (ADR-0025) is not on the critical path.
55+
56+
## Consequences
57+
58+
- A solution declares its languages once; the model page is solution-complete
59+
(links all surfaces) even though each port emits only its own api surface.
60+
- One-tree (relative) and federated multi-repo (`baseUrl`) topologies are the same
61+
mechanism, differing only by whether a `baseUrl` is set.
62+
- No model-doc duplication across languages; the Tier-2/Tier-1 split is preserved
63+
(model surface shared, api surfaces per-port).
64+
- SP-2 (Java api surface) plugs into this contract by emitting into its `subDir`
65+
and cross-linking back to the shared model root.
66+
67+
## Alternatives considered
68+
69+
- **Per-language self-contained doc sets** (each language emits model + its api):
70+
rejected — duplicates the language-neutral model docs (drift + waste).
71+
- **A solution manifest as the primary declaration** (Backstage-style): deferred —
72+
it is a federation/portal layer above per-project generation; the explicit
73+
config list is the right foundation, with the resolver kept manifest-ready.
74+
75+
## Scope
76+
77+
Built in TypeScript (the only port with both surfaces today). The `apiSurfaces`
78+
schema, the model→multi-link rendering, `apiSurfaceHref`, and the multi-surface
79+
cross-link conformance gate are TS-now; they are the cross-port contract for the
80+
SP-2 fan-out.

0 commit comments

Comments
 (0)